# -*- coding: utf-8 -*-
# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014-2015 Brett Cannon <brett@python.org>
# Copyright (c) 2015 Simu Toni <simutoni@gmail.com>
# Copyright (c) 2015 Pavel Roskin <proski@gnu.org>
# Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
# Copyright (c) 2015 Cosmin Poieana <cmin@ropython.org>
# Copyright (c) 2015 Viorel Stirbu <viorels@gmail.com>
# Copyright (c) 2016, 2018 Jakub Wilk <jwilk@jwilk.net>
# Copyright (c) 2016-2017 Roy Williams <roy.williams.iii@gmail.com>
# Copyright (c) 2016 Roy Williams <rwilliams@lyft.com>
# Copyright (c) 2016 Łukasz Rogalski <rogalski.91@gmail.com>
# Copyright (c) 2016 Erik <erik.eriksson@yahoo.com>
# Copyright (c) 2017-2018 Ville Skyttä <ville.skytta@iki.fi>
# Copyright (c) 2017 Daniel Miller <millerdev@gmail.com>
# Copyright (c) 2017 hippo91 <guillaume.peillex@gmail.com>
# Copyright (c) 2017 ahirnish <ahirnish@gmail.com>
# Copyright (c) 2018-2020 Anthony Sottile <asottile@umich.edu>
# Copyright (c) 2018 sbagan <pnlbagan@gmail.com>
# Copyright (c) 2018 Lucas Cimon <lucas.cimon@gmail.com>
# Copyright (c) 2018 Aivar Annamaa <aivarannamaa@users.noreply.github.com>
# Copyright (c) 2018 ssolanki <sushobhitsolanki@gmail.com>
# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com>
# Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk>
# Copyright (c) 2018 gaurikholkar <f2013002@goa.bits-pilani.ac.in>
# Copyright (c) 2019 Nick Drozd <nicholasdrozd@gmail.com>
# Copyright (c) 2019 Hugues Bruant <hugues.bruant@affirm.com>
# Copyright (c) 2019 Gabriel R Sezefredo <gabriel@sezefredo.com.br>
# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
# Copyright (c) 2019 Pierre Sassoulas <pierre.sassoulas@gmail.com>
# Copyright (c) 2019 bluesheeptoken <louis.fruleux1@gmail.com>
# Copyright (c) 2020 谭九鼎 <109224573@qq.com>
# Copyright (c) 2020 Federico Bond <federicobond@gmail.com>
# Copyright (c) 2020 Athos Ribeiro <athoscr@fedoraproject.org>
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# For details: https://github.com/PyCQA/pylint/blob/master/COPYING

"""Check Python 2 code for Python 2/3 source-compatible issues."""
import itertools
import re
import tokenize
from collections import namedtuple

import astroid
from astroid import bases

from pylint import checkers, interfaces
from pylint.checkers import utils
from pylint.checkers.utils import find_try_except_wrapper_node, node_ignores_exception
from pylint.constants import WarningScope
from pylint.interfaces import INFERENCE, INFERENCE_FAILURE

_ZERO = re.compile("^0+$")


def _is_old_octal(literal):
    if _ZERO.match(literal):
        return False
    if re.match(r"0\d+", literal):
        try:
            int(literal, 8)
        except ValueError:
            return False
        return True
    return None


def _inferred_value_is_dict(value):
    if isinstance(value, astroid.Dict):
        return True
    return isinstance(value, astroid.Instance) and "dict" in value.basenames


def _infer_if_relevant_attr(node, whitelist):
    return node.expr.infer() if node.attrname in whitelist else []


def _is_builtin(node):
    return getattr(node, "name", None) in ("__builtin__", "builtins")


_ACCEPTS_ITERATOR = {
    "iter",
    "list",
    "tuple",
    "sorted",
    "set",
    "sum",
    "any",
    "all",
    "enumerate",
    "dict",
    "filter",
    "reversed",
    "max",
    "min",
    "frozenset",
    "OrderedDict",
}
ATTRIBUTES_ACCEPTS_ITERATOR = {"join", "from_iterable"}
_BUILTIN_METHOD_ACCEPTS_ITERATOR = {
    "builtins.list.extend",
    "builtins.dict.update",
    "builtins.set.update",
}
DICT_METHODS = {"items", "keys", "values"}


def _in_iterating_context(node):
    """Check if the node is being used as an iterator.

    Definition is taken from lib2to3.fixer_util.in_special_context().
    """
    parent = node.parent
    # Since a call can't be the loop variant we only need to know if the node's
    # parent is a 'for' loop to know it's being used as the iterator for the
    # loop.
    if isinstance(parent, astroid.For):
        return True
    # Need to make sure the use of the node is in the iterator part of the
    # comprehension.
    if isinstance(parent, astroid.Comprehension):
        if parent.iter == node:
            return True
    # Various built-ins can take in an iterable or list and lead to the same
    # value.
    elif isinstance(parent, astroid.Call):
        if isinstance(parent.func, astroid.Name):
            if parent.func.name in _ACCEPTS_ITERATOR:
                return True
        elif isinstance(parent.func, astroid.Attribute):
            if parent.func.attrname in ATTRIBUTES_ACCEPTS_ITERATOR:
                return True

        inferred = utils.safe_infer(parent.func)
        if inferred:
            if inferred.qname() in _BUILTIN_METHOD_ACCEPTS_ITERATOR:
                return True
            root = inferred.root()
            if root and root.name == "itertools":
                return True
    # If the call is in an unpacking, there's no need to warn,
    # since it can be considered iterating.
    elif isinstance(parent, astroid.Assign) and isinstance(
        parent.targets[0], (astroid.List, astroid.Tuple)
    ):
        if len(parent.targets[0].elts) > 1:
            return True
    # If the call is in a containment check, we consider that to
    # be an iterating context
    elif (
        isinstance(parent, astroid.Compare)
        and len(parent.ops) == 1
        and parent.ops[0][0] in ["in", "not in"]
    ):
        return True
    # Also if it's an `yield from`, that's fair
    elif isinstance(parent, astroid.YieldFrom):
        return True
    if isinstance(parent, astroid.Starred):
        return True
    return False


def _is_conditional_import(node):
    """Checks if an import node is in the context of a conditional."""
    parent = node.parent
    return isinstance(
        parent, (astroid.TryExcept, astroid.ExceptHandler, astroid.If, astroid.IfExp)
    )


Branch = namedtuple("Branch", ["node", "is_py2_only"])


class Python3Checker(checkers.BaseChecker):

    __implements__ = interfaces.IAstroidChecker
    enabled = False
    name = "python3"

    msgs = {
        # Errors for what will syntactically break in Python 3, warnings for
        # everything else.
        "E1601": (
            "print statement used",
            "print-statement",
            "Used when a print statement is used "
            "(`print` is a function in Python 3)",
        ),
        "E1602": (
            "Parameter unpacking specified",
            "parameter-unpacking",
            "Used when parameter unpacking is specified for a function"
            "(Python 3 doesn't allow it)",
        ),
        "E1603": (
            "Implicit unpacking of exceptions is not supported in Python 3",
            "unpacking-in-except",
            "Python3 will not allow implicit unpacking of "
            "exceptions in except clauses. "
            "See https://www.python.org/dev/peps/pep-3110/",
            {"old_names": [("W0712", "old-unpacking-in-except")]},
        ),
        "E1604": (
            "Use raise ErrorClass(args) instead of raise ErrorClass, args.",
            "old-raise-syntax",
            "Used when the alternate raise syntax "
            "'raise foo, bar' is used "
            "instead of 'raise foo(bar)'.",
            {"old_names": [("W0121", "old-old-raise-syntax")]},
        ),
        "E1605": (
            "Use of the `` operator",
            "backtick",
            'Used when the deprecated "``" (backtick) operator is used '
            "instead  of the str() function.",
            {"scope": WarningScope.NODE, "old_names": [("W0333", "old-backtick")]},
        ),
        "E1609": (
            "Import * only allowed at module level",
            "import-star-module-level",
            "Used when the import star syntax is used somewhere "
            "else than the module level.",
            {"maxversion": (3, 0)},
        ),
        "W1601": (
            "apply built-in referenced",
            "apply-builtin",
            "Used when the apply built-in function is referenced "
            "(missing from Python 3)",
        ),
        "W1602": (
            "basestring built-in referenced",
            "basestring-builtin",
            "Used when the basestring built-in function is referenced "
            "(missing from Python 3)",
        ),
        "W1603": (
            "buffer built-in referenced",
            "buffer-builtin",
            "Used when the buffer built-in function is referenced "
            "(missing from Python 3)",
        ),
        "W1604": (
            "cmp built-in referenced",
            "cmp-builtin",
            "Used when the cmp built-in function is referenced "
            "(missing from Python 3)",
        ),
        "W1605": (
            "coerce built-in referenced",
            "coerce-builtin",
            "Used when the coerce built-in function is referenced "
            "(missing from Python 3)",
        ),
        "W1606": (
            "execfile built-in referenced",
            "execfile-builtin",
            "Used when the execfile built-in function is referenced "
            "(missing from Python 3)",
        ),
        "W1607": (
            "file built-in referenced",
            "file-builtin",
            "Used when the file built-in function is referenced "
            "(missing from Python 3)",
        ),
        "W1608": (
            "long built-in referenced",
            "long-builtin",
            "Used when the long built-in function is referenced "
            "(missing from Python 3)",
        ),
        "W1609": (
            "raw_input built-in referenced",
            "raw_input-builtin",
            "Used when the raw_input built-in function is referenced "
            "(missing from Python 3)",
        ),
        "W1610": (
            "reduce built-in referenced",
            "reduce-builtin",
            "Used when the reduce built-in function is referenced "
            "(missing from Python 3)",
        ),
        "W1611": (
            "StandardError built-in referenced",
            "standarderror-builtin",
            "Used when the StandardError built-in function is referenced "
            "(missing from Python 3)",
        ),
        "W1612": (
            "unicode built-in referenced",
            "unicode-builtin",
            "Used when the unicode built-in function is referenced "
            "(missing from Python 3)",
        ),
        "W1613": (
            "xrange built-in referenced",
            "xrange-builtin",
            "Used when the xrange built-in function is referenced "
            "(missing from Python 3)",
        ),
        "W1614": (
            "__coerce__ method defined",
            "coerce-method",
            "Used when a __coerce__ method is defined "
            "(method is not used by Python 3)",
        ),
        "W1615": (
            "__delslice__ method defined",
            "delslice-method",
            "Used when a __delslice__ method is defined "
            "(method is not used by Python 3)",
        ),
        "W1616": (
            "__getslice__ method defined",
            "getslice-method",
            "Used when a __getslice__ method is defined "
            "(method is not used by Python 3)",
        ),
        "W1617": (
            "__setslice__ method defined",
            "setslice-method",
            "Used when a __setslice__ method is defined "
            "(method is not used by Python 3)",
        ),
        "W1618": (
            "import missing `from __future__ import absolute_import`",
            "no-absolute-import",
            "Used when an import is not accompanied by "
            "``from __future__ import absolute_import`` "
            "(default behaviour in Python 3)",
        ),
        "W1619": (
            "division w/o __future__ statement",
            "old-division",
            "Used for non-floor division w/o a float literal or "
            "``from __future__ import division`` "
            "(Python 3 returns a float for int division unconditionally)",
        ),
        "W1620": (
            "Calling a dict.iter*() method",
            "dict-iter-method",
            "Used for calls to dict.iterkeys(), itervalues() or iteritems() "
            "(Python 3 lacks these methods)",
        ),
        "W1621": (
            "Calling a dict.view*() method",
            "dict-view-method",
            "Used for calls to dict.viewkeys(), viewvalues() or viewitems() "
            "(Python 3 lacks these methods)",
        ),
        "W1622": (
            "Called a next() method on an object",
            "next-method-called",
            "Used when an object's next() method is called "
            "(Python 3 uses the next() built-in function)",
        ),
        "W1623": (
            "Assigning to a class's __metaclass__ attribute",
            "metaclass-assignment",
            "Used when a metaclass is specified by assigning to __metaclass__ "
            "(Python 3 specifies the metaclass as a class statement argument)",
        ),
        "W1624": (
            "Indexing exceptions will not work on Python 3",
            "indexing-exception",
            "Indexing exceptions will not work on Python 3. Use "
            "`exception.args[index]` instead.",
            {"old_names": [("W0713", "old-indexing-exception")]},
        ),
        "W1625": (
            "Raising a string exception",
            "raising-string",
            "Used when a string exception is raised. This will not "
            "work on Python 3.",
            {"old_names": [("W0701", "old-raising-string")]},
        ),
        "W1626": (
            "reload built-in referenced",
            "reload-builtin",
            "Used when the reload built-in function is referenced "
            "(missing from Python 3). You can use instead imp.reload "
            "or importlib.reload.",
        ),
        "W1627": (
            "__oct__ method defined",
            "oct-method",
            "Used when an __oct__ method is defined "
            "(method is not used by Python 3)",
        ),
        "W1628": (
            "__hex__ method defined",
            "hex-method",
            "Used when a __hex__ method is defined (method is not used by Python 3)",
        ),
        "W1629": (
            "__nonzero__ method defined",
            "nonzero-method",
            "Used when a __nonzero__ method is defined "
            "(method is not used by Python 3)",
        ),
        "W1630": (
            "__cmp__ method defined",
            "cmp-method",
            "Used when a __cmp__ method is defined (method is not used by Python 3)",
        ),
        # 'W1631': replaced by W1636
        "W1632": (
            "input built-in referenced",
            "input-builtin",
            "Used when the input built-in is referenced "
            "(backwards-incompatible semantics in Python 3)",
        ),
        "W1633": (
            "round built-in referenced",
            "round-builtin",
            "Used when the round built-in is referenced "
            "(backwards-incompatible semantics in Python 3)",
        ),
        "W1634": (
            "intern built-in referenced",
            "intern-builtin",
            "Used when the intern built-in is referenced "
            "(Moved to sys.intern in Python 3)",
        ),
        "W1635": (
            "unichr built-in referenced",
            "unichr-builtin",
            "Used when the unichr built-in is referenced (Use chr in Python 3)",
        ),
        "W1636": (
            "map built-in referenced when not iterating",
            "map-builtin-not-iterating",
            "Used when the map built-in is referenced in a non-iterating "
            "context (returns an iterator in Python 3)",
            {"old_names": [("W1631", "implicit-map-evaluation")]},
        ),
        "W1637": (
            "zip built-in referenced when not iterating",
            "zip-builtin-not-iterating",
            "Used when the zip built-in is referenced in a non-iterating "
            "context (returns an iterator in Python 3)",
        ),
        "W1638": (
            "range built-in referenced when not iterating",
            "range-builtin-not-iterating",
            "Used when the range built-in is referenced in a non-iterating "
            "context (returns a range in Python 3)",
        ),
        "W1639": (
            "filter built-in referenced when not iterating",
            "filter-builtin-not-iterating",
            "Used when the filter built-in is referenced in a non-iterating "
            "context (returns an iterator in Python 3)",
        ),
        "W1640": (
            "Using the cmp argument for list.sort / sorted",
            "using-cmp-argument",
            "Using the cmp argument for list.sort or the sorted "
            "builtin should be avoided, since it was removed in "
            "Python 3. Using either `key` or `functools.cmp_to_key` "
            "should be preferred.",
        ),
        "W1641": (
            "Implementing __eq__ without also implementing __hash__",
            "eq-without-hash",
            "Used when a class implements __eq__ but not __hash__.  In Python 2, objects "
            "get object.__hash__ as the default implementation, in Python 3 objects get "
            "None as their default __hash__ implementation if they also implement __eq__.",
        ),
        "W1642": (
            "__div__ method defined",
            "div-method",
            "Used when a __div__ method is defined.  Using `__truediv__` and setting"
            "__div__ = __truediv__ should be preferred."
            "(method is not used by Python 3)",
        ),
        "W1643": (
            "__idiv__ method defined",
            "idiv-method",
            "Used when an __idiv__ method is defined.  Using `__itruediv__` and setting"
            "__idiv__ = __itruediv__ should be preferred."
            "(method is not used by Python 3)",
        ),
        "W1644": (
            "__rdiv__ method defined",
            "rdiv-method",
            "Used when a __rdiv__ method is defined.  Using `__rtruediv__` and setting"
            "__rdiv__ = __rtruediv__ should be preferred."
            "(method is not used by Python 3)",
        ),
        "W1645": (
            "Exception.message removed in Python 3",
            "exception-message-attribute",
            "Used when the message attribute is accessed on an Exception.  Use "
            "str(exception) instead.",
        ),
        "W1646": (
            "non-text encoding used in str.decode",
            "invalid-str-codec",
            "Used when using str.encode or str.decode with a non-text encoding.  Use "
            "codecs module to handle arbitrary codecs.",
        ),
        "W1647": (
            "sys.maxint removed in Python 3",
            "sys-max-int",
            "Used when accessing sys.maxint.  Use sys.maxsize instead.",
        ),
        "W1648": (
            "Module moved in Python 3",
            "bad-python3-import",
            "Used when importing a module that no longer exists in Python 3.",
        ),
        "W1649": (
            "Accessing a deprecated function on the string module",
            "deprecated-string-function",
            "Used when accessing a string function that has been deprecated in Python 3.",
        ),
        "W1650": (
            "Using str.translate with deprecated deletechars parameters",
            "deprecated-str-translate-call",
            "Used when using the deprecated deletechars parameters from str.translate.  Use "
            "re.sub to remove the desired characters ",
        ),
        "W1651": (
            "Accessing a deprecated function on the itertools module",
            "deprecated-itertools-function",
            "Used when accessing a function on itertools that has been removed in Python 3.",
        ),
        "W1652": (
            "Accessing a deprecated fields on the types module",
            "deprecated-types-field",
            "Used when accessing a field on types that has been removed in Python 3.",
        ),
        "W1653": (
            "next method defined",
            "next-method-defined",
            "Used when a next method is defined that would be an iterator in Python 2 but "
            "is treated as a normal function in Python 3.",
        ),
        "W1654": (
            "dict.items referenced when not iterating",
            "dict-items-not-iterating",
            "Used when dict.items is referenced in a non-iterating "
            "context (returns an iterator in Python 3)",
        ),
        "W1655": (
            "dict.keys referenced when not iterating",
            "dict-keys-not-iterating",
            "Used when dict.keys is referenced in a non-iterating "
            "context (returns an iterator in Python 3)",
        ),
        "W1656": (
            "dict.values referenced when not iterating",
            "dict-values-not-iterating",
            "Used when dict.values is referenced in a non-iterating "
            "context (returns an iterator in Python 3)",
        ),
        "W1657": (
            "Accessing a removed attribute on the operator module",
            "deprecated-operator-function",
            "Used when accessing a field on operator module that has been "
            "removed in Python 3.",
        ),
        "W1658": (
            "Accessing a removed attribute on the urllib module",
            "deprecated-urllib-function",
            "Used when accessing a field on urllib module that has been "
            "removed or moved in Python 3.",
        ),
        "W1659": (
            "Accessing a removed xreadlines attribute",
            "xreadlines-attribute",
            "Used when accessing the xreadlines() function on a file stream, "
            "removed in Python 3.",
        ),
        "W1660": (
            "Accessing a removed attribute on the sys module",
            "deprecated-sys-function",
            "Used when accessing a field on sys module that has been "
            "removed in Python 3.",
        ),
        "W1661": (
            "Using an exception object that was bound by an except handler",
            "exception-escape",
            "Emitted when using an exception, that was bound in an except "
            "handler, outside of the except handler. On Python 3 these "
            "exceptions will be deleted once they get out "
            "of the except handler.",
        ),
        "W1662": (
            "Using a variable that was bound inside a comprehension",
            "comprehension-escape",
            "Emitted when using a variable, that was bound in a comprehension "
            "handler, outside of the comprehension itself. On Python 3 these "
            "variables will be deleted outside of the "
            "comprehension.",
        ),
    }

    _bad_builtins = frozenset(
        [
            "apply",
            "basestring",
            "buffer",
            "cmp",
            "coerce",
            "execfile",
            "file",
            "input",  # Not missing, but incompatible semantics
            "intern",
            "long",
            "raw_input",
            "reduce",
            "round",  # Not missing, but incompatible semantics
            "StandardError",
            "unichr",
            "unicode",
            "xrange",
            "reload",
        ]
    )

    _unused_magic_methods = frozenset(
        [
            "__coerce__",
            "__delslice__",
            "__getslice__",
            "__setslice__",
            "__oct__",
            "__hex__",
            "__nonzero__",
            "__cmp__",
            "__div__",
            "__idiv__",
            "__rdiv__",
        ]
    )

    _invalid_encodings = frozenset(
        [
            "base64_codec",
            "base64",
            "base_64",
            "bz2_codec",
            "bz2",
            "hex_codec",
            "hex",
            "quopri_codec",
            "quopri",
            "quotedprintable",
            "quoted_printable",
            "uu_codec",
            "uu",
            "zlib_codec",
            "zlib",
            "zip",
            "rot13",
            "rot_13",
        ]
    )

    _bad_python3_module_map = {
        "sys-max-int": {"sys": frozenset(["maxint"])},
        "deprecated-itertools-function": {
            "itertools": frozenset(
                ["izip", "ifilter", "imap", "izip_longest", "ifilterfalse"]
            )
        },
        "deprecated-types-field": {
            "types": frozenset(
                [
                    "EllipsisType",
                    "XRangeType",
                    "ComplexType",
                    "StringType",
                    "TypeType",
                    "LongType",
                    "UnicodeType",
                    "ClassType",
                    "BufferType",
                    "StringTypes",
                    "NotImplementedType",
                    "NoneType",
                    "InstanceType",
                    "FloatType",
                    "SliceType",
                    "UnboundMethodType",
                    "ObjectType",
                    "IntType",
                    "TupleType",
                    "ListType",
                    "DictType",
                    "FileType",
                    "DictionaryType",
                    "BooleanType",
                    "DictProxyType",
                ]
            )
        },
        "bad-python3-import": frozenset(
            [
                "anydbm",
                "BaseHTTPServer",
                "__builtin__",
                "CGIHTTPServer",
                "ConfigParser",
                "copy_reg",
                "cPickle",
                "cStringIO",
                "Cookie",
                "cookielib",
                "dbhash",
                "dumbdbm",
                "dumbdb",
                "Dialog",
                "DocXMLRPCServer",
                "FileDialog",
                "FixTk",
                "gdbm",
                "htmlentitydefs",
                "HTMLParser",
                "httplib",
                "markupbase",
                "Queue",
                "repr",
                "robotparser",
                "ScrolledText",
                "SimpleDialog",
                "SimpleHTTPServer",
                "SimpleXMLRPCServer",
                "StringIO",
                "dummy_thread",
                "SocketServer",
                "test.test_support",
                "Tkinter",
                "Tix",
                "Tkconstants",
                "tkColorChooser",
                "tkCommonDialog",
                "Tkdnd",
                "tkFileDialog",
                "tkFont",
                "tkMessageBox",
                "tkSimpleDialog",
                "UserList",
                "UserString",
                "whichdb",
                "_winreg",
                "xmlrpclib",
                "audiodev",
                "Bastion",
                "bsddb185",
                "bsddb3",
                "Canvas",
                "cfmfile",
                "cl",
                "commands",
                "compiler",
                "dircache",
                "dl",
                "exception",
                "fpformat",
                "htmllib",
                "ihooks",
                "imageop",
                "imputil",
                "linuxaudiodev",
                "md5",
                "mhlib",
                "mimetools",
                "MimeWriter",
                "mimify",
                "multifile",
                "mutex",
                "new",
                "popen2",
                "posixfile",
                "pure",
                "rexec",
                "rfc822",
                "sets",
                "sha",
                "sgmllib",
                "sre",
                "stringold",
                "sunaudio",
                "sv",
                "test.testall",
                "thread",
                "timing",
                "toaiff",
                "user",
                "urllib2",
                "urlparse",
            ]
        ),
        "deprecated-string-function": {
            "string": frozenset(
                [
                    "maketrans",
                    "atof",
                    "atoi",
                    "atol",
                    "capitalize",
                    "expandtabs",
                    "find",
                    "rfind",
                    "index",
                    "rindex",
                    "count",
                    "lower",
                    "letters",
                    "split",
                    "rsplit",
                    "splitfields",
                    "join",
                    "joinfields",
                    "lstrip",
                    "rstrip",
                    "strip",
                    "swapcase",
                    "translate",
                    "upper",
                    "ljust",
                    "rjust",
                    "center",
                    "zfill",
                    "replace",
                    "lowercase",
                    "letters",
                    "uppercase",
                    "atol_error",
                    "atof_error",
                    "atoi_error",
                    "index_error",
                ]
            )
        },
        "deprecated-operator-function": {"operator": frozenset({"div"})},
        "deprecated-urllib-function": {
            "urllib": frozenset(
                {
                    "addbase",
                    "addclosehook",
                    "addinfo",
                    "addinfourl",
                    "always_safe",
                    "basejoin",
                    "ftpcache",
                    "ftperrors",
                    "ftpwrapper",
                    "getproxies",
                    "getproxies_environment",
                    "getproxies_macosx_sysconf",
                    "main",
                    "noheaders",
                    "pathname2url",
                    "proxy_bypass",
                    "proxy_bypass_environment",
                    "proxy_bypass_macosx_sysconf",
                    "quote",
                    "quote_plus",
                    "reporthook",
                    "splitattr",
                    "splithost",
                    "splitnport",
                    "splitpasswd",
                    "splitport",
                    "splitquery",
                    "splittag",
                    "splittype",
                    "splituser",
                    "splitvalue",
                    "unquote",
                    "unquote_plus",
                    "unwrap",
                    "url2pathname",
                    "urlcleanup",
                    "urlencode",
                    "urlopen",
                    "urlretrieve",
                }
            )
        },
        "deprecated-sys-function": {"sys": frozenset({"exc_clear"})},
    }

    _deprecated_attrs = frozenset(
        itertools.chain.from_iterable(
            attr
            for module_map in _bad_python3_module_map.values()
            if isinstance(module_map, dict)
            for attr in module_map.values()
        )
    )

    _relevant_call_attrs = (
        DICT_METHODS | _deprecated_attrs | {"encode", "decode", "translate"}
    )

    _python_2_tests = frozenset(
        [
            astroid.extract_node(x).repr_tree()
            for x in [
                "sys.version_info[0] == 2",
                "sys.version_info[0] < 3",
                "sys.version_info == (2, 7)",
                "sys.version_info <= (2, 7)",
                "sys.version_info < (3, 0)",
            ]
        ]
    )

    def __init__(self, *args, **kwargs):
        self._future_division = False
        self._future_absolute_import = False
        self._modules_warned_about = set()
        self._branch_stack = []
        super().__init__(*args, **kwargs)

    # pylint: disable=keyword-arg-before-vararg, arguments-differ
    def add_message(self, msg_id, always_warn=False, *args, **kwargs):
        if always_warn or not (
            self._branch_stack and self._branch_stack[-1].is_py2_only
        ):
            super().add_message(msg_id, *args, **kwargs)

    def _is_py2_test(self, node):
        if isinstance(node.test, astroid.Attribute) and isinstance(
            node.test.expr, astroid.Name
        ):
            if node.test.expr.name == "six" and node.test.attrname == "PY2":
                return True
        elif (
            isinstance(node.test, astroid.Compare)
            and node.test.repr_tree() in self._python_2_tests
        ):
            return True
        return False

    def visit_if(self, node):
        self._branch_stack.append(Branch(node, self._is_py2_test(node)))

    def leave_if(self, node):
        assert self._branch_stack.pop().node == node

    def visit_ifexp(self, node):
        self._branch_stack.append(Branch(node, self._is_py2_test(node)))

    def leave_ifexp(self, node):
        assert self._branch_stack.pop().node == node

    def visit_module(self, node):  # pylint: disable=unused-argument
        """Clear checker state after previous module."""
        self._future_division = False
        self._future_absolute_import = False

    def visit_functiondef(self, node):
        if node.is_method():
            if node.name in self._unused_magic_methods:
                method_name = node.name
                if node.name.startswith("__"):
                    method_name = node.name[2:-2]
                self.add_message(method_name + "-method", node=node)
            elif node.name == "next":
                # If there is a method named `next` declared, if it is invokable
                # with zero arguments then it implements the Iterator protocol.
                # This means if the method is an instance method or a
                # classmethod 1 argument should cause a failure, if it is a
                # staticmethod 0 arguments should cause a failure.
                failing_arg_count = 1
                if utils.decorated_with(node, [bases.BUILTINS + ".staticmethod"]):
                    failing_arg_count = 0
                if len(node.args.args) == failing_arg_count:
                    self.add_message("next-method-defined", node=node)

    @utils.check_messages("parameter-unpacking")
    def visit_arguments(self, node):
        for arg in node.args:
            if isinstance(arg, astroid.Tuple):
                self.add_message("parameter-unpacking", node=arg)

    @utils.check_messages("comprehension-escape")
    def visit_listcomp(self, node):
        names = {
            generator.target.name
            for generator in node.generators
            if isinstance(generator.target, astroid.AssignName)
        }
        scope = node.parent.scope()
        scope_names = scope.nodes_of_class(astroid.Name, skip_klass=astroid.FunctionDef)
        has_redefined_assign_name = any(
            assign_name
            for assign_name in scope.nodes_of_class(
                astroid.AssignName, skip_klass=astroid.FunctionDef
            )
            if assign_name.name in names and assign_name.lineno > node.lineno
        )
        if has_redefined_assign_name:
            return

        emitted_for_names = set()
        scope_names = list(scope_names)
        for scope_name in scope_names:
            if (
                scope_name.name not in names
                or scope_name.lineno <= node.lineno
                or scope_name.name in emitted_for_names
                or scope_name.scope() == node
            ):
                continue

            emitted_for_names.add(scope_name.name)
            self.add_message("comprehension-escape", node=scope_name)

    def visit_name(self, node):
        """Detect when a "bad" built-in is referenced."""
        found_node, _ = node.lookup(node.name)
        if not _is_builtin(found_node):
            return
        if node.name not in self._bad_builtins:
            return
        if node_ignores_exception(node) or isinstance(
            find_try_except_wrapper_node(node), astroid.ExceptHandler
        ):
            return

        message = node.name.lower() + "-builtin"
        self.add_message(message, node=node)

    @utils.check_messages("print-statement")
    def visit_print(self, node):
        self.add_message("print-statement", node=node, always_warn=True)

    def _warn_if_deprecated(self, node, module, attributes, report_on_modules=True):
        for message, module_map in self._bad_python3_module_map.items():
            if module in module_map and module not in self._modules_warned_about:
                if isinstance(module_map, frozenset):
                    if report_on_modules:
                        self._modules_warned_about.add(module)
                        self.add_message(message, node=node)
                elif attributes and module_map[module].intersection(attributes):
                    self.add_message(message, node=node)

    def visit_importfrom(self, node):
        if node.modname == "__future__":
            for name, _ in node.names:
                if name == "division":
                    self._future_division = True
                elif name == "absolute_import":
                    self._future_absolute_import = True
        else:
            if not self._future_absolute_import:
                if self.linter.is_message_enabled("no-absolute-import"):
                    self.add_message("no-absolute-import", node=node)
                    self._future_absolute_import = True
            if not _is_conditional_import(node) and not node.level:
                self._warn_if_deprecated(node, node.modname, {x[0] for x in node.names})

        if node.names[0][0] == "*":
            if self.linter.is_message_enabled("import-star-module-level"):
                if not isinstance(node.scope(), astroid.Module):
                    self.add_message("import-star-module-level", node=node)

    def visit_import(self, node):
        if not self._future_absolute_import:
            if self.linter.is_message_enabled("no-absolute-import"):
                self.add_message("no-absolute-import", node=node)
                self._future_absolute_import = True
        if not _is_conditional_import(node):
            for name, _ in node.names:
                self._warn_if_deprecated(node, name, None)

    @utils.check_messages("metaclass-assignment")
    def visit_classdef(self, node):
        if "__metaclass__" in node.locals:
            self.add_message("metaclass-assignment", node=node)
        locals_and_methods = set(node.locals).union(x.name for x in node.mymethods())
        if "__eq__" in locals_and_methods and "__hash__" not in locals_and_methods:
            self.add_message("eq-without-hash", node=node)

    @utils.check_messages("old-division")
    def visit_binop(self, node):
        if not self._future_division and node.op == "/":
            for arg in (node.left, node.right):
                inferred = utils.safe_infer(arg)
                # If we can infer the object and that object is not an int, bail out.
                if inferred and not (
                    (
                        isinstance(inferred, astroid.Const)
                        and isinstance(inferred.value, int)
                    )
                    or (
                        isinstance(inferred, astroid.Instance)
                        and inferred.name == "int"
                    )
                ):
                    break
            else:
                self.add_message("old-division", node=node)

    def _check_cmp_argument(self, node):
        # Check that the `cmp` argument is used
        kwargs = []
        if isinstance(node.func, astroid.Attribute) and node.func.attrname == "sort":
            inferred = utils.safe_infer(node.func.expr)
            if not inferred:
                return

            builtins_list = "{}.list".format(bases.BUILTINS)
            if isinstance(inferred, astroid.List) or inferred.qname() == builtins_list:
                kwargs = node.keywords

        elif isinstance(node.func, astroid.Name) and node.func.name == "sorted":
            inferred = utils.safe_infer(node.func)
            if not inferred:
                return

            builtins_sorted = "{}.sorted".format(bases.BUILTINS)
            if inferred.qname() == builtins_sorted:
                kwargs = node.keywords

        for kwarg in kwargs or []:
            if kwarg.arg == "cmp":
                self.add_message("using-cmp-argument", node=node)
                return

    @staticmethod
    def _is_constant_string_or_name(node):
        if isinstance(node, astroid.Const):
            return isinstance(node.value, str)
        return isinstance(node, astroid.Name)

    @staticmethod
    def _is_none(node):
        return isinstance(node, astroid.Const) and node.value is None

    @staticmethod
    def _has_only_n_positional_args(node, number_of_args):
        return len(node.args) == number_of_args and all(node.args) and not node.keywords

    @staticmethod
    def _could_be_string(inferred_types):
        confidence = INFERENCE if inferred_types else INFERENCE_FAILURE
        for inferred_type in inferred_types:
            if inferred_type is astroid.Uninferable:
                confidence = INFERENCE_FAILURE
            elif not (
                isinstance(inferred_type, astroid.Const)
                and isinstance(inferred_type.value, str)
            ):
                return None
        return confidence

    def visit_call(self, node):
        self._check_cmp_argument(node)

        if isinstance(node.func, astroid.Attribute):
            inferred_types = set()

            try:
                for inferred_receiver in _infer_if_relevant_attr(
                    node.func, self._relevant_call_attrs
                ):
                    if inferred_receiver is astroid.Uninferable:
                        continue
                    inferred_types.add(inferred_receiver)
                    if isinstance(inferred_receiver, astroid.Module):
                        self._warn_if_deprecated(
                            node,
                            inferred_receiver.name,
                            {node.func.attrname},
                            report_on_modules=False,
                        )
                    if (
                        _inferred_value_is_dict(inferred_receiver)
                        and node.func.attrname in DICT_METHODS
                    ):
                        if not _in_iterating_context(node):
                            checker = "dict-{}-not-iterating".format(node.func.attrname)
                            self.add_message(checker, node=node)
            except astroid.InferenceError:
                pass
            if node.args:
                is_str_confidence = self._could_be_string(inferred_types)
                if is_str_confidence:
                    if (
                        node.func.attrname in ("encode", "decode")
                        and len(node.args) >= 1
                        and node.args[0]
                    ):
                        first_arg = node.args[0]
                        self._validate_encoding(first_arg, node)
                    if (
                        node.func.attrname == "translate"
                        and self._has_only_n_positional_args(node, 2)
                        and self._is_none(node.args[0])
                        and self._is_constant_string_or_name(node.args[1])
                    ):
                        # The above statement looking for calls of the form:
                        #
                        # foo.translate(None, 'abc123')
                        #
                        # or
                        #
                        # foo.translate(None, some_variable)
                        #
                        # This check is somewhat broad and _may_ have some false positives, but
                        # after checking several large codebases it did not have any false
                        # positives while finding several real issues.  This call pattern seems
                        # rare enough that the trade off is worth it.
                        self.add_message(
                            "deprecated-str-translate-call",
                            node=node,
                            confidence=is_str_confidence,
                        )
                return
            if node.keywords:
                return
            if node.func.attrname == "next":
                self.add_message("next-method-called", node=node)
            elif node.func.attrname in ("iterkeys", "itervalues", "iteritems"):
                self.add_message("dict-iter-method", node=node)
            elif node.func.attrname in ("viewkeys", "viewvalues", "viewitems"):
                self.add_message("dict-view-method", node=node)
        elif isinstance(node.func, astroid.Name):
            found_node = node.func.lookup(node.func.name)[0]
            if _is_builtin(found_node):
                if node.func.name in ("filter", "map", "range", "zip"):
                    if not _in_iterating_context(node):
                        checker = "{}-builtin-not-iterating".format(node.func.name)
                        self.add_message(checker, node=node)
                elif node.func.name == "open" and node.keywords:
                    kwargs = node.keywords
                    for kwarg in kwargs or []:
                        if kwarg.arg == "encoding":
                            self._validate_encoding(kwarg.value, node)
                            break

    def _validate_encoding(self, encoding, node):
        if isinstance(encoding, astroid.Const):
            value = encoding.value
            if value in self._invalid_encodings:
                self.add_message("invalid-str-codec", node=node)

    @utils.check_messages("indexing-exception")
    def visit_subscript(self, node):
        """ Look for indexing exceptions. """
        try:
            for inferred in node.value.infer():
                if not isinstance(inferred, astroid.Instance):
                    continue
                if utils.inherit_from_std_ex(inferred):
                    self.add_message("indexing-exception", node=node)
        except astroid.InferenceError:
            return

    def visit_assignattr(self, node):
        if isinstance(node.assign_type(), astroid.AugAssign):
            self.visit_attribute(node)

    def visit_delattr(self, node):
        self.visit_attribute(node)

    @utils.check_messages("exception-message-attribute", "xreadlines-attribute")
    def visit_attribute(self, node):
        """Look for removed attributes"""
        if node.attrname == "xreadlines":
            self.add_message("xreadlines-attribute", node=node)
            return

        exception_message = "message"
        try:
            for inferred in _infer_if_relevant_attr(
                node, self._deprecated_attrs | {exception_message}
            ):
                if isinstance(inferred, astroid.Instance) and utils.inherit_from_std_ex(
                    inferred
                ):
                    if node.attrname == exception_message:

                        # Exceptions with .message clearly defined are an exception
                        if exception_message in inferred.instance_attrs:
                            continue
                        self.add_message("exception-message-attribute", node=node)
                if isinstance(inferred, astroid.Module):
                    self._warn_if_deprecated(
                        node, inferred.name, {node.attrname}, report_on_modules=False
                    )
        except astroid.InferenceError:
            return

    @utils.check_messages("unpacking-in-except", "comprehension-escape")
    def visit_excepthandler(self, node):
        """Visit an except handler block and check for exception unpacking."""

        def _is_used_in_except_block(node, block):
            current = node
            while current and current is not block:
                current = current.parent
            return current is not None

        if isinstance(node.name, (astroid.Tuple, astroid.List)):
            self.add_message("unpacking-in-except", node=node)
            return

        if not node.name:
            return

        # Find any names
        scope = node.parent.scope()
        scope_names = scope.nodes_of_class(astroid.Name, skip_klass=astroid.FunctionDef)
        scope_names = list(scope_names)
        potential_leaked_names = [
            scope_name
            for scope_name in scope_names
            if scope_name.name == node.name.name
            and scope_name.lineno > node.lineno
            and not _is_used_in_except_block(scope_name, node)
        ]
        reassignments_for_same_name = {
            assign_name.lineno
            for assign_name in scope.nodes_of_class(
                astroid.AssignName, skip_klass=astroid.FunctionDef
            )
            if assign_name.name == node.name.name
        }
        for leaked_name in potential_leaked_names:
            if any(
                node.lineno < elem < leaked_name.lineno
                for elem in reassignments_for_same_name
            ):
                continue
            self.add_message("exception-escape", node=leaked_name)

    @utils.check_messages("backtick")
    def visit_repr(self, node):
        self.add_message("backtick", node=node)

    @utils.check_messages("raising-string", "old-raise-syntax")
    def visit_raise(self, node):
        """Visit a raise statement and check for raising
        strings or old-raise-syntax.
        """

        # Ignore empty raise.
        if node.exc is None:
            return
        expr = node.exc
        if self._check_raise_value(node, expr):
            return
        try:
            value = next(astroid.unpack_infer(expr))
        except astroid.InferenceError:
            return
        self._check_raise_value(node, value)

    def _check_raise_value(self, node, expr):
        if isinstance(expr, astroid.Const):
            value = expr.value
            if isinstance(value, str):
                self.add_message("raising-string", node=node)
                return True
        return None


class Python3TokenChecker(checkers.BaseTokenChecker):
    __implements__ = interfaces.ITokenChecker
    name = "python3"
    enabled = False

    msgs = {
        "E1606": (
            "Use of long suffix",
            "long-suffix",
            'Used when "l" or "L" is used to mark a long integer. '
            "This will not work in Python 3, since `int` and `long` "
            "types have merged.",
            {"maxversion": (3, 0)},
        ),
        "E1607": (
            "Use of the <> operator",
            "old-ne-operator",
            'Used when the deprecated "<>" operator is used instead '
            'of "!=". This is removed in Python 3.',
            {"maxversion": (3, 0), "old_names": [("W0331", "old-old-ne-operator")]},
        ),
        "E1608": (
            "Use of old octal literal",
            "old-octal-literal",
            "Used when encountering the old octal syntax, "
            "removed in Python 3. To use the new syntax, "
            "prepend 0o on the number.",
            {"maxversion": (3, 0)},
        ),
        "E1610": (
            "Non-ascii bytes literals not supported in 3.x",
            "non-ascii-bytes-literal",
            "Used when non-ascii bytes literals are found in a program. "
            "They are no longer supported in Python 3.",
            {"maxversion": (3, 0)},
        ),
    }

    def process_tokens(self, tokens):
        for idx, (tok_type, token, start, _, _) in enumerate(tokens):
            if tok_type == tokenize.NUMBER:
                if token.lower().endswith("l"):
                    # This has a different semantic than lowercase-l-suffix.
                    self.add_message("long-suffix", line=start[0])
                elif _is_old_octal(token):
                    self.add_message("old-octal-literal", line=start[0])
            if tokens[idx][1] == "<>":
                self.add_message("old-ne-operator", line=tokens[idx][2][0])
            if tok_type == tokenize.STRING and token.startswith("b"):
                if any(elem for elem in token if ord(elem) > 127):
                    self.add_message("non-ascii-bytes-literal", line=start[0])


def register(linter):
    linter.register_checker(Python3Checker(linter))
    linter.register_checker(Python3TokenChecker(linter))
